import {exec,execSync} from "child_process";
import util from "util";
import fs from "fs";
import path from 'path';
import {createLogger} from "./Logger.js";
import {StrUtil} from "./StrUtil.js";

const execPromise = util.promisify(exec);

const log = createLogger("Git");

const FAST_CLONE_ARGS = ['--single-branch', '--depth=1', '--filter=blob:none'];

function toText(value) {
    return typeof value === 'string' ? value : '';
}

function extractGitErrorOutput(error) {
    if (!error) {
        return '';
    }
    const parts = [];
    const stderr = toText(error.stderr);
    const stdout = toText(error.stdout);
    const message = toText(error.message);
    if (stderr) parts.push(stderr);
    if (stdout) parts.push(stdout);
    if (message && !parts.includes(message)) parts.push(message);
    return parts
        .join('\n')
        .split(/\r?\n/)
        .map(line => line.trimEnd())
        .filter(line => line.length > 0)
        .join('\n');
}

function isPermissionDeniedOutput(output) {
    if (!output) {
        return false;
    }
    const normalized = output.toLowerCase();
    const keywords = [
        'could not read from remote repository',
        'project you were looking for could not be found',
        'permission denied',
        'access rights',
        'repository not found',
        'authentication failed'
    ];
    return keywords.some(keyword => normalized.includes(keyword));
}

function resolveRepoUrl(repoUrl, options = {}) {
    const targetProtocol = options.targetProtocol || (options.forceSsh ? 'ssh' : options.forceHttp ? 'http' : null);
    if (targetProtocol === 'ssh') {
        const sshUrl = StrUtil.toSshUrl(repoUrl);
        if (StrUtil.notNull(sshUrl) && sshUrl !== repoUrl) {
            return {
                repoUrl: sshUrl,
                converted: true,
                protocol: 'ssh'
            };
        }
    } else if (targetProtocol === 'http') {
        const httpUrl = StrUtil.toHttpUrl(repoUrl);
        if (StrUtil.notNull(httpUrl) && httpUrl !== repoUrl) {
            return {
                repoUrl: httpUrl,
                converted: true,
                protocol: 'http'
            };
        }
    }
    return {
        repoUrl,
        converted: false,
        protocol: repoUrl.startsWith('git@') ? 'ssh' : /^https?:\/\//i.test(repoUrl) ? 'http' : 'unknown'
    };
}

function buildCloneCommand({ repoUrl, branch, targetDir, usePartialClone = false }) {
    const args = ['git', 'clone'];
    if (usePartialClone) {
        args.push(...FAST_CLONE_ARGS);
    }
    if (StrUtil.notNull(branch)) {
        args.push('-b', branch);
    }
    args.push(repoUrl);
    if (StrUtil.notNull(targetDir)) {
        args.push(targetDir);
    }
    return args.join(' ');
}

export class GitUtil {
    /**
     * 克隆仓库（指定分支和目录）
     * @param {string} repoUrl SSH 仓库地址
     * @param {string} branch 分支名
     * @param {string} targetDir 下载目录
     */
    static async clone(repoUrl, branch, targetDir, options = {}) {
        let finalRepoUrl = repoUrl;
        try {
            // 如果目标目录已存在，检查是否为空
            if (fs.existsSync(targetDir)) {
                const files = fs.readdirSync(targetDir);
                if (files.length > 0) {
                    log.info(`目录已存在 ${targetDir} ，跳过`)
                    return true;
                }
            } else {
                fs.mkdirSync(targetDir, {recursive: true});
            }
            const usePartialClone = Boolean(options.usePartialClone);
            if (usePartialClone) {
                log.info(`启用快速克隆参数: ${FAST_CLONE_ARGS.join(' ')}`);
            }
            const resolved = resolveRepoUrl(repoUrl, options);
            finalRepoUrl = resolved.repoUrl;
            if (resolved.converted) {
                const protocolLabel = resolved.protocol === 'http' ? 'HTTPS' : 'SSH';
                log.info(`仓库地址已转换为 ${protocolLabel}: ${finalRepoUrl}`);
            }
            const cmd = buildCloneCommand({ repoUrl: finalRepoUrl, branch, targetDir, usePartialClone });
            log.info("正在 clone :", finalRepoUrl);
            const {stdout, stderr} = await execPromise(cmd);
            if (StrUtil.notNull(stdout)) log.info(stdout);
            if (StrUtil.notNull(stderr)) log.info(stderr);
            log.success(` ${StrUtil.getRepoName(finalRepoUrl)} 克隆成功！`);
            return true;
        } catch (err) {
            const output = extractGitErrorOutput(err);
            if (output) {
                log.error(output);
            } else {
                log.error("克隆失败:", err.message);
            }
            const permissionDenied = isPermissionDeniedOutput(output);
            if (permissionDenied && typeof options.onPermissionDenied === 'function') {
                options.onPermissionDenied({
                    repoUrl: finalRepoUrl,
                    message: output || err.message || ''
                });
            }
            if (typeof options.onCloneFailed === 'function') {
                options.onCloneFailed({
                    repoUrl: finalRepoUrl,
                    error: err,
                    message: output || err.message || '',
                    permissionDenied
                });
            }
            return false;
        }
    }

    /**
     * 克隆仓库（默认分支或服务端默认配置）
     * @param {string} repoUrl SSH 仓库地址
     * @param {string|null} targetDir 克隆到的目标目录，可为空
     * @returns {Promise<boolean>} 是否克隆成功（若已存在则视为成功）
     */
    static async cloneDirect(repoUrl, targetDir = null, options = {}) {
        let finalRepoUrl = repoUrl;
        try {
            if (StrUtil.isNull(repoUrl)) {
                log.error("仓库地址为空，无法克隆");
                return false;
            }
            const resolved = resolveRepoUrl(repoUrl, options);
            finalRepoUrl = resolved.repoUrl;
            if (resolved.converted) {
                const protocolLabel = resolved.protocol === 'http' ? 'HTTPS' : 'SSH';
                log.info(`仓库地址已转换为 ${protocolLabel}: ${finalRepoUrl}`);
            }
            const repoName = StrUtil.getRepoName(finalRepoUrl);
            const directoryName = targetDir || repoName;
            if (StrUtil.notNull(directoryName)) {
                const resolvedDir = path.resolve(process.cwd(), directoryName);
                if (fs.existsSync(resolvedDir)) {
                    log.info(`目录已存在 ${resolvedDir} ，跳过`);
                    return true;
                }
            }
            const usePartialClone = Boolean(options.usePartialClone);
            if (usePartialClone) {
                log.info(`启用快速克隆参数: ${FAST_CLONE_ARGS.join(' ')}`);
            }
            const cmd = buildCloneCommand({ repoUrl: finalRepoUrl, branch: null, targetDir, usePartialClone });
            log.info("正在 clone :", finalRepoUrl);
            const {stdout, stderr} = await execPromise(cmd);
            if (StrUtil.notNull(stdout)) log.info(stdout);
            if (StrUtil.notNull(stderr)) log.info(stderr);
            log.success(` ${StrUtil.getRepoName(finalRepoUrl)} 克隆成功！`);
            return true;
        } catch (err) {
            const output = extractGitErrorOutput(err);
            if (output) {
                log.error(output);
            } else {
                log.error("克隆失败:", err.message);
            }
            const permissionDenied = isPermissionDeniedOutput(output);
            if (permissionDenied && typeof options.onPermissionDenied === 'function') {
                options.onPermissionDenied({
                    repoUrl: finalRepoUrl,
                    message: output || err.message || ''
                });
            }
            if (typeof options.onCloneFailed === 'function') {
                options.onCloneFailed({
                    repoUrl: finalRepoUrl,
                    error: err,
                    message: output || err.message || '',
                    permissionDenied
                });
            }
            return false;
        }
    }


    /**
     * 获取指定 Git 仓库目录当前分支远端最新的提交信息
     * @param {string} repoDir Git 仓库目录路径
     * @returns {{hash: string, message: string, branch: string} | null}
     */
    static getRemoteLatestCommit(repoDir) {
        if (!repoDir || !fs.existsSync(path.join(repoDir, '.git'))) {
            throw new Error(`${repoDir} 不是一个有效的 git 仓库`);
        }

        try {
            // 当前分支
            const branch = execSync(`git -C "${repoDir}" rev-parse --abbrev-ref HEAD`, { encoding: 'utf8' }).trim();

            // 更新远端引用，但不打印 fetch 输出
            execSync(`git -C "${repoDir}" fetch origin ${branch}`, { stdio: ['ignore', 'ignore', 'pipe'] });

            // 获取远端最新 commit hash
            const hash = execSync(`git -C "${repoDir}" rev-parse origin/${branch}`, { encoding: 'utf8' }).trim();

            // 获取远端最新 commit message
            const message = execSync(`git -C "${repoDir}" log -1 --pretty=%B origin/${branch}`, { encoding: 'utf8' }).trim();

            return { branch, hash, message };
        } catch (err) {
            console.error('获取远端最新 commit 失败:', err.message);
            return null;
        }
    }
    /**
     * 获取指定 Git 仓库目录当前本地分支名字
     * @param {string} repoDir Git 仓库目录路径
     * @returns {string | null} 分支名字
     */
    static getLocalBranch(repoDir) {
        if (!repoDir || !fs.existsSync(path.join(repoDir, '.git'))) {
            throw new Error(`${repoDir} 不是一个有效的 git 仓库`);
        }

        try {
            // 获取当前分支名字
            const branch = execSync(`git -C "${repoDir}" rev-parse --abbrev-ref HEAD`, { encoding: 'utf8' }).trim();
            return branch;
        } catch (err) {
            console.error('获取本地分支失败:', err.message);
            return null;
        }
    }

}
